﻿#!/usr/bin/env python3
# -*- coding: utf-8 -*-

#第十二章 内置模块
def splitout(name):
	print("------------------%s-------------------"%name)
#1.)datetime
splitout("datetimer")
from datetime import datetime

now = datetime.now()#当前时间; 注意不是时间戳
print("now=",now)
date = datetime(2016,5,3,21,10,50)#获取指定时间;顺序是:年月日 时分秒
print(date)

#datetime和timestamp(也就是时间戳)相互转换
t = date.timestamp()
print(t) #单位秒 浮点数，小数部分表示毫秒

date = datetime.fromtimestamp(t) #会自动转换成本地时区
print(date)
stddate = datetime.utcfromtimestamp(t) #格林威治标准时间
print(stddate)

print("标准时间和北京时间的差=",abs(stddate-date),abs(stddate.timestamp()-date.timestamp()))

#str和datetime的相互转换

s = "2015-05-01 20:50:20"

date = datetime.strptime(s,"%Y-%m-%d %H:%M:%S") #字符串转换成日期 具体转换字符格式看文档
print(date)

print(date.strftime("%A %m %I %H")) #把时间转换成字符串

#加减操作 timedelta的使用
from datetime import timedelta

print(date,(date-timedelta(days=2,hours=2)))

#本地时间转换为UTC时间; 一个datetime类型有一个时区属性tzinfo，但是默认为None，所以无法区分这个datetime到底是哪个时区，除非强行给datetime设置一个时区
from datetime import timezone

tz_utc_8 = timezone(timedelta(hours=8)) #创建时区

now = datetime.now()
print("now=",now,now.tzinfo)
dt = now.replace(tzinfo=tz_utc_8) # 强制设置为UTC+8:00
print("dt=",dt.tzinfo)

#时区转换
#先获取utc时间 然后再转换成任意时区的时间
utcNow = datetime.utcnow().replace(tzinfo=timezone.utc);
print("utcNow 11=",utcNow)
bj_dt = utcNow.astimezone(timezone(timedelta(hours=8)))
print("bj_dt=",bj_dt)


#把任意时间转换成北京时间的函数
def toChinaTime(dt):
	dt.replace(tzinfo=timezone.utc)
	dt = dt.astimezone(timezone(timedelta(hours=8)))
	return dt
print("utcNow 22=",utcNow)
print("toChinaTime=",toChinaTime(utcNow))

'''
练习
假设你获取了用户输入的日期和时间如2015-1-21 9:01:30，以及一个时区信息如UTC+5:00，均是str，请编写一个函数将其转换为timestamp：
'''

#2.)collections 集合模块
splitout("collections 集合模块")
from collections import namedtuple

#namedtuple;是一个函数可以定义一个tuple对象并且规定元素的个数，并且可以用属性来访问元素。、
#说白了就是自定义一个类型数据，但是该数据是基于tuple类型的

#这里的名称之所以用Point是为了说明类的名称是Point2但是类引用是Point；这样写是没有问题的 但是一般情况下是引用和名称一样
Point = namedtuple("Point2",['x','y','z','name']) #自定义一个Poin类型

p = Point(10,20,30,'')
#p.name = "age"; 所以属性只能在初始化的时候设置
print('p=',p,p.x,p.y,p.z)
print("检查p的类型",isinstance(p,Point))
print("检查p的类型",isinstance(p,tuple))

#deque; list是线性储存,访问速度快 但是插入和删除速度慢;deque是为了高效实现插入和删除的双向列表,适用于队列和栈
from collections import deque

q1 = deque(['a','b','c'])
q1.append('x') #追加到末尾
q1.appendleft('y') #添加到开头
q1.pop()  #删除最后一个元素
q1.popleft() #删除第一个元素
print(q1)

#defaultdict 当key不存在时dict会抛出keyError defaultdict可返回自定义的默认值
from collections import defaultdict
defdict = defaultdict(lambda:'none')

defdict['key1'] = 'hello';
print(defdict['key1'])
print(defdict['key2'])

#OrderedDict key是有序的dict,顺序按照插入顺序
from collections import OrderedDict

d1 = dict([('a',1),('b',2),('c',3)])
print('d1 的key是无序的=',d1)
ordDict = OrderedDict([('a',1),('b',2),('c',3)])
print("ordDict的输出是按照插入顺序=",ordDict)

print('a' in ordDict) #dict判断key是否存在

#用OrderedDict实现一个先进先出(FIFO)的dict
class FIFODict(OrderedDict):
	def __init__(self,capacity):
		super(FIFODict,self).__init__()
		self._capacity = capacity
	
	def __setitem__(self,key,value):
		containsKey = 1 if key in self else 0
		print("length = ",(key,len(self)))
		if len(self)>=self._capacity:
			#如果last=false，popitem函数就返回最先进入的元素。
			last = self.popitem(last=False) #last为True是LIFO,即为堆栈，反之是FIFO，即为队列 
			print("remove last:",(key,last))		
		OrderedDict.__setitem__(self,key,value) #已经存在的key直接覆盖

fifod = FIFODict(5)

fifod['a'] = 1;
fifod['b'] = 2;
fifod['c'] = 3;
fifod['d'] = 4;
fifod['e'] = 5;
fifod['c'] = 6;
print('fifod没满=',fifod)
fifod['f'] = 6;
fifod['g'] = 7;
print('fifod已经满=',fifod)

#Counter计数器,是dict的子类
#主要是计算字符串中某个字符出现的次数,dict中某个key出现的次数
from collections import Counter
splitout("Counter计数器")
str1 = "zhouhongzhou"
cret = Counter(str1)
print(type(cret),isinstance(cret,dict))
print(cret)#字符串为键数量为值

#base64编码 ;Base64是一种用64个字符来表示任意二进制数据的方法
splitout("base64编码")
import base64

bstr=base64.b64encode(b'binary\x00string')
print(bstr)
s = base64.b64decode(bstr)
print(s)

urlss = base64.urlsafe_b64encode(b"iwww.bai\+/")
print(urlss)
 
#struct模块 个struct 模块来解决bytes 和其他二进制数据类型的转换。
splitout("struct模块")

import struct

byte1 = struct.pack(">I",256)
print(byte1)
print(struct.unpack(">I",byte1))

#hashlib md5和sha1算法

import hashlib

md5 = hashlib.md5()
#必须先编码 再传入
md5.update('abc'.encode('utf-8'))		#通过update函数多次连接串和一次连接一个串结果是一样的
md5.update('_def'.encode('utf-8'))
mdstr1 = md5.hexdigest()
print(mdstr1) #32位16进制

md5 = hashlib.md5()
md5.update("abc_def".encode('utf-8')) 
mdstr2 = md5.hexdigest()
print(mdstr2)

sha1 = hashlib.sha1();

sha1.update("6548python".encode('utf-8'))
sha1str1 = sha1.hexdigest();
print(sha1str1) #40位16进制

#itertools 迭代器工具类,主要返回一个Iterator
splitout("itertools模块")

import itertools

#返回所以自然数
#natuals = itertools.count(1) #用for循环就可以无限的迭代下去
#cycle = itertools.cycle("abc") #用for循环就可以无限重复一次输出a,b,c

#rep = itertools.repeat('a') #无限重复
#rep = itertools.repeat('a',3) #重复三次

for i in itertools.chain('abc','xyz'): #串联两个迭代器 生成一个更大的
	print(i)

for key,group in itertools.groupby('aaabbcccdddssaasasae'): #把迭代器中相邻重复的元素分组
	print(key,list(group))

#操作xml 
splitout("操作xml")
'''
操作XML有两种方法：DOM和SAX。DOM会把整个XML读入内存，解析为树，因此占用内存大，解析慢，优点是可以任
意遍历树的节点。SAX是流模式，边读边解析，占用内存小，解析快，缺点是我们需要自己处理事件。
'''
#正常情况下优先考虑使用SAX,因为DOM会占用很大的内存

#SAX；是基于事件的处理方式
'''
比如读取:
<a href="/">python</a>
会产生3个事件：
1. start_element事件，在读取<a href="/"> 时；
2. char_data事件，在读取python 时；
3. end_element事件，在读取</a> 时。
'''

#xml格式说明不能和节点同行 
xmlStr = r'''<?xml version='1.0'?> 
<root>
	<item id='1'/>
	<item id='2' >武器</item>
	<item id='3' >
		<sub with = '600' />
		<sub with = '500' />
	</item>
	<item id='4' >
		<sub with = '700' />
		<sub with = '800' />
	</item>
</root>
'''
print(xmlStr)

from xml.parsers.expat import ParserCreate
from collections import namedtuple

class XmlElement(object):
	def __init__(self,attr,char):
		self.attr = attr
		self.char = char

#解释xml的类
class DFSaxXmlParser(object):
	
	def __init__(self,xml):
		self._xmlData = dict();
		self._xml = xml;
		self._curNode='';
		self._curIndex = -1;		
		self.parse(self._xml);
		
	def parse(self,xml):
		p = ParserCreate()
		p.StartElementHandler = self.start_element
		p.EndElementHandler = self.end_element
		p.CharacterDataHandler = self.char_data
		p.Parse(xml)
		
	def getCurNodes(self):
		if self._curNode in self._xmlData:
			curNodes = self._xmlData[self._curNode]
		else:
			curNodes = [];
			self._xmlData[self._curNode] = curNodes;		
		return curNodes
	
	def start_element(self,name,attrs):
		print("start_element:",(name,attrs,type(attrs)))	
		if self._curNode!=name:
			split= "." if self._curNode!="" else "";
			#self._curNode = self._curNode+split+name;
			self._curNode = name
			self._curIndex = 0;
		
		curNodes = self.getCurNodes()
		line = XmlElement(attrs,'')
		curNodes.append(line)
		self._curLine = line
		
	def char_data(self,text):
		print("char_data:",("字符为空" if text=="" else text),self._curIndex,self._curNode)		
		if self._curLine:
			self._curLine.char=self._curLine.char+text
			print("char_data: self._curLine.char=",self._curLine.char,self._curLine.attr)
		
	def end_element(self,name):
		print("end_element:",name)
	@property
	def xmlData(self):
		if not hasattr(self,"_xmlData"):
			self._xmlData = dict();
		return self._xmlData
	

dfSax = DFSaxXmlParser(xmlStr)
splitout("items")
items = dfSax.xmlData['item']
print(type(items))
for i,v in enumerate(items):	
	print("items=","char len=",len(items[i].char),'attr=',items[i].attr,'char=',items[i].char)

#HTMLParser 解释html
splitout('#HTMLParser 解释html')
from html.parser import HTMLParser
from html.entities import name2codepoint

class MyHtmlParser(HTMLParser):
	def tracetag(self,tag,fn):
		print("<%s>"%tag,'|;=== from %s'%fn)
		
	def handle_starttag(self,tag,attrs):
		self.tracetag(tag,'handle_starttag')
	
	def handle_endtag(self,tag):
		self.tracetag(tag,'handle_endtag')
	
	def handle_startendtag(self,tag,attrs):
		self.tracetag(tag,'handle_startendtag')
	
	def handle_data(self,data):
		dstr = data.encode('utf-8')
		print("handle_data data=",dstr,type(dstr)) #如果只是直接print(data)会导致编码问题,所以先解码再输出
		
	#抓取注释
	def handle_comment(self,data):
		print("handle_comment;=== <!--",data,"-->")
	
	def handle_entityref(self,name):
		print("handle_entityref=:&%s:"%name)
	#读取特殊字符 ： 特殊字符有两种，一种是英文表示的&nbsp; ，一种是数字表示的&#1234;
	def handle_charref(self,name):
		print("handle_charref=:&#%s",name)
	
	
parser = MyHtmlParser();

html1 = '''
<html>
	<head></head>
	<body>
		<!-- 测试解释html-->
		
		<div>header</div>
		<p><a href='#www.baidu.com' >百度</a> HTML&nbsp;.......教程</p>
	</body>
</html>
'''

html2 = '''
<html>
<head></head>
<body>
<!-- test html parser -->
<p>Some <a href=\"#\">html</a> HTML&nbsp;tutorial...<br>END</p>
</body></html>'''

parser.feed(html1)

#urllib提供了一系列用于操作URL的功能
splitout('urllib模块')
from urllib import request,parse
import os


with request.urlopen('https://www.hao123.com/') as f:
	#data = f.read()
	headers = f.getheaders()	
	for k,v in headers:
		print(k,':',v)


#用get请求模拟iPhone6请求豆瓣首页

req = request.Request('http://www.douban.com/');

#伪装成浏览器
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')

with request.urlopen(req) as f:
	print('status:',f.status,f.reason)
	
	data = f.read().decode('utf-8')
	ROOT_PATH = "D:/workspace/python/learnpy/iofile/";
	furl = os.path.join(ROOT_PATH,"douban.html")
	with open(furl,'w') as f:		
		f.write(data) #返回豆瓣的iPhone手机版

#post请求; 如果要以POST发送一个请求，只需要把参数data以bytes形式传入。


splitout('post请求模拟登录微博')
''''
email = input('email:')
psw = input('psw:')

login_data = parse.urlencode(
[
	('username',email),
	('password',psw),
	('entry','mweibo'),
	('client_id',''),
	('savestate','1'),
	('ec',''),
	('pagerefer','https://passport.weibo.cn/signin/welcome?entry=mweibo&r=http%3A%2F%2Fm.weibo.cn%2F')
]
)
#设置请求头文件
req = request.Request('https://passport.weibo.cn/sso/login')
req.add_header('Origin', 'https://passport.weibo.cn')
req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25')
req.add_header('Referer', 'https://passport.weibo.cn/signin/login?entry=mweibo&res=wel&wm=3349&r=http%3A%2F%2Fm.weibo.cn%2F')

#通过login_data.encode('utf-8')把数据转换成字节码传入
with request.urlopen(req,data=login_data.encode('utf-8')) as f:
	print('status:',f.status,f.reason)
	print('post data:',f.read().decode('utf-8'))

'''



#Handler

#如果还需要更复杂的控制，比如通过一个Proxy去访问网站，我们需要利用ProxyHandler来处理，示例代码如下：
#通过代理网络登录网站

'''
proxy_handler = urllib.request.ProxyHandler({'http': 'http://www.example.com:3128/'})
proxy_auth_handler = urllib.request.ProxyBasicAuthHandler()
proxy_auth_handler.add_password('realm', 'host', 'username', 'password')
opener = urllib.request.build_opener(proxy_handler, proxy_auth_handler)
with opener.open('http://www.example.com/login.html') as f:
    pass
'''





